{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Functions and Descriptors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As I mentioned in the lecture video, Python functions actually implement the non-data descriptor protocol, i.e. they implement the `__get__` method"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "def add(a, b):\n",
    "    return a + b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "hasattr(add, '__get__')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So what does that `__get__` actually return?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We know the arguments for `__get__` are `self, instance, owner_class`, so let's try to call the `__get__` method with `instance` set to `None` and `owner_class` set to our main module:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "me = sys.modules['__main__']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = add.__get__(None, me)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(<function __main__.add(a, b)>, 140554287212472)"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p, id(p)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(<function __main__.add(a, b)>, 140554287212472)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add, id(add)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, when `instance` is `None`, the `__get__` method just returns the function itself, with owner set to `__main__` in this case."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's see what happens when we define a function inside a class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "        \n",
    "    def say_hello(self):\n",
    "        return f'{self.name} says hello'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's first access the `say_hello` callable from the class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function __main__.Person.say_hello(self)>"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Person.say_hello"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see the owner class is now `__main__.Person`, and we get a plain function back."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What essentially happened is that when we retrieved the attribute `say_hello` from the `Person` class, since functions are descriptors, Python called the `__get__` method, in this case with `instance` set to `None`, and the owner class set to the `Person` class."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And when we call it from an instance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = Person('Alex')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'0x7fd5585f5470'"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "hex(id(p))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<bound method Person.say_hello of <__main__.Person object at 0x7fd5585f5470>>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.say_hello"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Again, since `say_hello` is actually a descriptor, Python invoked the `__get__` method, this time with an instance (`p`) and with owner class set to `Person`.\n",
    "\n",
    "The descriptor then returns a method object, which it binds to the instance."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So we could retrieve it this way too:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "bound_method = Person.say_hello.__get__(p, Person)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<bound method Person.say_hello of <__main__.Person object at 0x7fd5585f5470>>"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bound_method"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Alex says hello'"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.say_hello()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Alex says hello'"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bound_method()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So the question is, since `p.say_hello`, a non-data descriptor, does not return a function, but a `method` object, where is the *actual* function stored?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Turns out methods have a special attribute, `__func__` that is is used to keep a reference to the original function that can then be called when needed:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(<function __main__.Person.say_hello(self)>, 140554287397880)"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.say_hello.__func__, id(p.say_hello.__func__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, `__func__` is a reference to the `say_hello` function object defined in the `Person` class, and to make sure we can do this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.say_hello.__func__ is Person.say_hello"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We could try to mimic this behavior ourselves by writing our own descriptor. The problem is that we need to define a function using Python functions, so this is a bit circular, but we can try to somewhat mimic instance methods to gain a better understanding of how they work."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's say we want to mimic something like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "        \n",
    "    def say_hello(self):\n",
    "        return f'{self.name} says hello!'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We want to write a descriptor to replace `say_hello`.\n",
    "\n",
    "First we're going to write a plain function, directly in our main module:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "def say_hello(self):\n",
    "    if self and hasattr(self, 'name'):\n",
    "        return f'{self.name} says hello!'\n",
    "    else:\n",
    "        return 'Hello!'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can call this as an ordinary function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Hello!'"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "say_hello(None)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But what we really want is to make a descriptor that either returns the function itself when accessed via the class it is contained in (`Person` in this case), or a bound method when it is accessed via an instance of that class."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First a slight detour to look at method types."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A `method` is an actual type in Python, and it is available in the `types` module:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "import types"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on class method in module builtins:\n",
      "\n",
      "class method(object)\n",
      " |  method(function, instance)\n",
      " |  \n",
      " |  Create a bound instance method object.\n",
      " |  \n",
      " |  Methods defined here:\n",
      " |  \n",
      " |  __call__(self, /, *args, **kwargs)\n",
      " |      Call self as a function.\n",
      " |  \n",
      " |  __delattr__(self, name, /)\n",
      " |      Implement delattr(self, name).\n",
      " |  \n",
      " |  __eq__(self, value, /)\n",
      " |      Return self==value.\n",
      " |  \n",
      " |  __ge__(self, value, /)\n",
      " |      Return self>=value.\n",
      " |  \n",
      " |  __get__(self, instance, owner, /)\n",
      " |      Return an attribute of instance, which is of type owner.\n",
      " |  \n",
      " |  __getattribute__(self, name, /)\n",
      " |      Return getattr(self, name).\n",
      " |  \n",
      " |  __gt__(self, value, /)\n",
      " |      Return self>value.\n",
      " |  \n",
      " |  __hash__(self, /)\n",
      " |      Return hash(self).\n",
      " |  \n",
      " |  __le__(self, value, /)\n",
      " |      Return self<=value.\n",
      " |  \n",
      " |  __lt__(self, value, /)\n",
      " |      Return self<value.\n",
      " |  \n",
      " |  __ne__(self, value, /)\n",
      " |      Return self!=value.\n",
      " |  \n",
      " |  __new__(*args, **kwargs) from builtins.type\n",
      " |      Create and return a new object.  See help(type) for accurate signature.\n",
      " |  \n",
      " |  __reduce__(...)\n",
      " |      helper for pickle\n",
      " |  \n",
      " |  __repr__(self, /)\n",
      " |      Return repr(self).\n",
      " |  \n",
      " |  __setattr__(self, name, value, /)\n",
      " |      Implement setattr(self, name, value).\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data descriptors defined here:\n",
      " |  \n",
      " |  __func__\n",
      " |      the function (or other callable) implementing a method\n",
      " |  \n",
      " |  __self__\n",
      " |      the instance to which a method is bound\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(types.MethodType)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see the constructor for the `MethodType` requires a function, and an object to bind it to."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's try this out:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __init__(self, name):\n",
    "        self.name = name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = Person('Alex')\n",
    "m = types.MethodType(say_hello, p)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(<__main__.Person at 0x7fd5585f5358>,\n",
       " <bound method say_hello of <__main__.Person object at 0x7fd5585f5358>>)"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p, m"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see, `m` is a `method` object, bound to the object `p`. And we can call this method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Alex says hello!'"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "m()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ok, so now we can start planning how we are going to implement our descriptor.\n",
    "\n",
    "When the `__get__` method is called from the class, we will want to return the plain `say_hello` function. But when `__get__` is called from an instance we'll want to return a method object bound to the specific instance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyFunc:\n",
    "    def __init__(self, func):\n",
    "        self._func = func\n",
    "    \n",
    "    def __get__(self, instance, owner):\n",
    "        if instance is None:\n",
    "            # called from class\n",
    "            print('__get__ called from class')\n",
    "            return self._func\n",
    "        else:\n",
    "            # called from instance\n",
    "            print('__get__ called from an instance')\n",
    "            return types.MethodType(self._func, instance)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "I made a slight tweak here to allow us to specify any function we want in the init - this make this descriptor a little more generic."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's go ahead and use that in a class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "def hello(self):\n",
    "    print(f'{self.name} says hello!')\n",
    "    \n",
    "class Person:\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "        \n",
    "    say_hello = MyFunc(hello)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's see what happens when we access `say_hello` from the class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "__get__ called from class\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<function __main__.hello(self)>"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Person.say_hello"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We get the original function back.\n",
    "\n",
    "And when we access it from an instance of `Person`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "__get__ called from an instance\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<bound method hello of <__main__.Person object at 0x7fd5585f5d68>>"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p = Person('Alex')\n",
    "p.say_hello"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We get a bound method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "__get__ called from an instance\n",
      "Alex says hello!\n"
     ]
    }
   ],
   "source": [
    "p.say_hello()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Moreover, the original function `hello` is referenced by the bound method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "__get__ called from an instance\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<function __main__.hello(self)>"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.say_hello.__func__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hopefully it is now a little clearer how methods actually work in Python!"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
